function getTreeInfo2( params ) {    

    //local vars that will be set to globals.treeInfo after the tree has been traversed
    var treeInfo = {
        tips: 0,
        longestTraversal: 0,
        rootLabelLength: ( params.tree.name ) ? getWordLength( { word: params.tree.name, canvas: globals.r } ).width : 0,
        generations: 0,
        longestChildLabel: 0,
        longestChildName: undefined };
        
    var branchLengths = true;

    //used to set on each clade for node selector sort
    var topologicalSortIndex = 0;

    //function to recurse through tree
    var fun = function( p ) {

        p.depth++;

        var currentClade = p.clade;

        //at the moment we require all nodes except the root to have a length in order to support scaled branching
        if( branchLengths && currentClade.length == 'None' && (!currentClade.isRoot) ) { branchLengths = false; }


        if( branchLengths && !currentClade.isRoot ) { p.lengthFromRoot = p.lengthFromRoot + parseFloat( currentClade.length ); }

        //internal node
        if( currentClade.children && currentClade.children.length ) {

            for( var i = 0, ii = currentClade.children.length; i < ii; i++ ) { 
                fun( { clade: currentClade.children[i], depth : p.depth, lengthFromRoot: p.lengthFromRoot } );
            }

        //tip node
        } else {

            treeInfo.tips++;
            
            if( p.depth > treeInfo.generations ) { treeInfo.generations = p.depth; }

            //need to know longest child name in order to properly buffer visualization on the right hand side
            if( currentClade.name && ( currentClade.name.length > treeInfo.longestChildLabel ) ) {
                treeInfo.longestChildLabel = currentClade.name.length;
                treeInfo.longestChildName = currentClade.name;
            }

            //we care about the longest distance from the root if we have scaled branches
            if( branchLengths && ( p.lengthFromRoot > treeInfo.longestTraversal ) ) { treeInfo.longestTraversal = p.lengthFromRoot; }
        }
        
        currentClade.topoIndex = topologicalSortIndex++;
    }

    fun( { clade: params.tree, depth: 0, lengthFromRoot: 0 } );

    setGlobals( { treeInfo: treeInfo,
                  scaledBranches: branchLengths } );
}

var getWordLength = function( p ) {

    var localConfig = config;

    var temp =
        drawCladeLabel( { label: { x: 0, y: 0, text: p.word, widthTest: true,
                                   isInternal: false, size: p.size },
                          canvas: p.canvas,
                          localConfig: { labelColor: localConfig.labelColor.value,
                                         fontSize: localConfig.fontSize.value,
                                         fontFaily: localConfig.fontFamily.value,
                                         buttonRadius: localConfig.buttonRadius.value,
                                         labelBuffer: localConfig.labelBuffer.value } } );

    var box = temp.getBBox();
    temp.remove();
    return { width: box.width, height: box.height };
}

function setCanvasSize( p ) {

    var treeInfo = p.global.treeInfo;
   
    var longestLabel = getWordLength( { word: treeInfo.longestChildName, size: p.config.fontSize.value * p.scaleValue.y, canvas: p.canvas } );

    treeInfo.longestChildLabel = longestLabel.width;
    treeInfo.exampleLabelHeight = longestLabel.height;

    var width = p.treeWidth + 
                ( p.config.canvasBuffer.value * 2 ) +
                ( p.config.labelBuffer.value ) +
                ( p.global.scrollBarWidth ) +
                ( treeInfo.longestChildLabel ) +
                ( treeInfo.rootLabelLength ) + 20; //add the 20 for the root

    var height = ( ( treeInfo.tips - 1 ) * p.config.nodeSeparation.value ) + ( longestLabel.height * 2 ) +
                 ( p.config.canvasBuffer.value * 2 );

    // 100% size
    p.global.canvas = { width: width, height: height };

    var zoomedSize = { x: width * p.scaleValue.x, y: height * p.scaleValue.y };

    $('#' + p.container).width( zoomedSize.x ).height( zoomedSize.y );

    p.canvas.setSize( zoomedSize.x, zoomedSize.y );
}

function removeExpandableIcons( p ) {

    var expandables = globals.expandable;

    for( var id in expandables.main ) {

        if( p.main ) {
            expandables.main[id].hide().remove();
            delete expandables.main[id];
        }
        
        if( p.overview ) {
            expandables.scaled[id].hide().remove();
            delete expandables.scaled[id];
        }
    }
}

function preRender( p ) {

    var currentClade = p.clade;
    var currentCoords = currentClade.coords = { };

    if( p.parent ) { currentClade.parent = p.parent; }

    if( p.scaledBranches ) {

        currentCoords.scaled = ( currentClade.isRoot )
            ? { x: p.currentTip.x - ( ( p.generation - 1 ) * p.generationSeparation ),
               dx: 0 }
            : { x: p.scaledParentX + ( currentClade.length * p.lengthMultiplier ),
               dx: -( currentClade.length * p.lengthMultiplier ) };

    }
        
    for( var i = 0, ii = currentClade.children.length; i < ii; i++ ) { 

        var child = currentClade.children[i];
        
        var scaledParentX = ( p.scaledBranches ) ? currentCoords.scaled.x : null;

        preRender( { clade: child,
                     parent: currentClade,
                     scaledParentX: scaledParentX,
                     scaledBranches: p.scaledBranches,
                     currentTip: p.currentTip,
                     scaleValue: p.scaleValue,
                     generationSeparation: p.generationSeparation,
                     lengthMultiplier: p.lengthMultiplier,
                     nodeSeparation: p.nodeSeparation,
                     supportValueInfo: p.supportValueInfo,
                     pathWidth: p.pathWidth,
                     labelBuffer: p.labelBuffer,
                     buttonRadius: p.buttonRadius,
                     labels: p.labels,
                     labelsToAdd: p.labelsToAdd,
                     overviewScale: p.overviewScale,
                     fontSize: p.fontSize,
                     canvas: p.canvas,
                     scaling: p.scaling,
                     pathStrings: p.pathStrings,
                     renderInfoFun: p.renderInfoFun,
                     generation: p.generation - 1 } );
                
    }

    if( p.clade.children.length ) {

        var firstChildCoords = currentClade.children[0].coords;

        currentCoords.unscaled = { x: firstChildCoords.unscaled.x + firstChildCoords.unscaled.dx,
                                  dx: -( p.generationSeparation ) };

        currentCoords.y = ( ( currentClade.children[ currentClade.children.length - 1 ].coords.y + firstChildCoords.y ) / 2 );

    } else {

        currentCoords.unscaled = { x: p.currentTip.x, dx: -( p.generationSeparation * p.generation ) };
        
        currentCoords.y = p.currentTip.y;

        p.currentTip.y = p.currentTip.y + p.nodeSeparation;
    }
    

    if( p.scaling == 'scaled' || p.scaling == 'unscaled' ) {    

        p.renderInfoFun( { clade: currentClade,
                           scaling: p.scaling,
                           overviewScale: p.overviewScale,
                           scaleValue: p.scaleValue,
                           supportValueInfo: p.supportValueInfo,
                           fontSize: p.fontSize,
                           buttonRadius: p.buttonRadius,
                           labelBuffer: p.labelBuffer,
                           labels: p.labels,
                           labelsToAdd: p.labelsToAdd,
                           pathWidth: p.pathWidth,
                           pathStrings: p.pathStrings,
                           canvas: p.canvas } );
    }
}

function drawExpandableIcon( p ) { 

    var expandables = globals.expandable[p.type];

    expandables[ p.cladeId ] =
        p.canvas.path( "M" + ( p.point.x - p.size) + " " + (p.point.y - p.size) +
                            "L" + (p.point.x + p.size) + " " + (p.point.y - p.size) +
                            "L" + p.point.x + " " + (p.point.y + p.size) + "z").attr( { stroke: 'red', fill: 'red' } );
}

function drawCladeLabel( p ) {

    var label = p.canvas.text( p.label.x, p.label.y, p.label.text ).attr(
        { stroke: p.localConfig.labelColor,
          fill: p.localConfig.labelColor,
          "font-family": p.localConfig.fontFamily,
          "font-size": (p.size) ? p.size : p.localConfig.fontSize,
          "text-anchor": "start" } );

    label.node.id = 'label' + p.label.id;
    label.isInternal = p.label.isInternal;

    var pointTranslation = getLabelPointTranslation( { isInternal: p.label.isInternal,
                                                       labelWidth: label.getBBox().width,
                                                       buttonRadius: p.localConfig.buttonRadius,
                                                       labelBuffer: p.localConfig.labelBuffer } );

    label.id = p.label.id;    
    label.translate( pointTranslation.dx, pointTranslation.dy );
    label.topoIndex = p.label.index;

    return label;
}

function getLabelPointTranslation( p ) {

    return ( p.isInternal )
        ? { dx: -( p.labelWidth + p.buttonRadius + p.labelBuffer ), dy: -( p.buttonRadius + p.labelBuffer ) }
        : { dx: ( p.buttonRadius + p.labelBuffer ), dy: 0 };
}

function findSmoothCoords( p ) {

    var currentClade = p.clade;
    var currentCoords = currentClade.coords;

    var renderInfoParams = { clade: null,
          scaling: p.scaling, overviewScale: p.overviewScale, scaleValue: p.scaleValue, supportValueInfo: p.supportValueInfo, fontSize: p.fontSize,
          buttonRadius: p.buttonRadius, labelBuffer: p.labelBuffer, labels: p.labels, pathWidth: p.pathWidth, pathStrings: p.pathStrings, canvas: p.canvas,
          labelsToAdd: p.labelsToAdd };

    currentCoords.smooth = { x: p.clade.coords.unscaled.x, dx: p.clade.coords.unscaled.dx };

    currentClade.leftmostChildX = p.leftmostStart;

    if( currentClade.children.length ) {

        for( var i = 0, ii = currentClade.children.length; i < ii; i++ ) {

            var child = currentClade.children[i];

            findSmoothCoords( { clade: child,
                                scaling: p.scaling,
                                overviewScale: p.overviewScale,
                                scaleValue: p.scaleValue,
                                supportValueInfo: p.supportValueInfo,
                                fontSize: p.fontSize,
                                buttonRadius: p.buttonRadius,
                                labelBuffer: p.labelBuffer,
                                labels: p.labels,
                                labelsToAdd: p.labelsToAdd,
                                pathWidth: p.pathWidth,
                                pathStrings: p.pathStrings,
                                canvas: p.canvas,
                                renderInfoFun: p.renderInfoFun,
                                unscaledParentX: currentCoords.unscaled.x,
                                leftmostStart: p.leftmostStart } );

            if( child.coords.unscaled.x < currentClade.leftmostChildX ) { currentClade.leftmostChildX = child.coords.unscaled.x; }
        }

        if( p.unscaledParentX ) {

            var smoothX = ( ( p.unscaledParentX + currentClade.leftmostChildX ) / 2 );

            currentCoords.smooth.x = smoothX;
            currentCoords.smooth.dx = p.unscaledParentX - smoothX;

            for( var i = 0, ii = currentClade.children.length; i < ii; i++ ) {

                var child = currentClade.children[i];

                child.coords.smooth.dx = smoothX - child.coords.smooth.x;

                if( p.scaling == 'smooth' ) {

                        renderInfoParams.clade = child;

                        p.renderInfoFun( renderInfoParams );
                }
            }
        }
    }

    if( p.scaling == 'smooth' && currentClade.isRoot ) {

        renderInfoParams.clade = currentClade;

        p.renderInfoFun( renderInfoParams );
            
        for( var i = 0, ii = currentClade.children.length; i < ii; i++ ) {
        
            renderInfoParams.clade = currentClade.children[i];

            p.renderInfoFun( renderInfoParams );
        }
    }
    
   delete currentClade.leftmostChildX;
}

function handleLabel( p ) {

    if( p.labels[ p.clade.id ] ) {

        translateLabel( { clade: p.clade,
                          label: p.labels[ p.clade.id ],
                          fontSize: p.fontSize,
                          buttonRadius: p.buttonRadius,
                          labelBuffer: p.labelBuffer,
                          scaling: p.scaling,
                          scaleValue: p.scaleValue } ); }

    else { 

        p.labelsToAdd.push( { x: p.clade.coords[p.scaling].x, y: p.clade.coords.y, index: p.clade.topoIndex, id: p.clade.id,
                           text: p.clade.name, isInternal: ( p.clade.children.length ) } );
    }
}

function translateLabel( p ) {

    p.label.attr( { 'font-size': p.fontSize * p.scaleValue.y, opacity: 1 } );

    var labelBox = p.label.getBBox();

    var pointTranslation = getLabelPointTranslation( { isInternal: p.clade.children.length,
                                                       labelWidth: labelBox.width,
                                                       buttonRadius: p.buttonRadius,
                                                       labelBuffer: p.labelBuffer } );

    p.label.translate( ( p.clade.coords[p.scaling].x * p.scaleValue.x ) - labelBox.x + pointTranslation.dx,
                       ( p.clade.coords.y * p.scaleValue.y ) - ( labelBox.y + (labelBox.height/2) ) + pointTranslation.dy );
}

function createTreePath( p ) { return p.canvas.path( p.string ).attr( p.attr ); }

function createProgressBar( p ) {

    var containerOffset = p.container.offset();
    var containerWidth = p.container.width();

    var progressBarWidth = containerWidth * .20;
    var progressBarLeft = containerOffset.left + (containerWidth/2) - (containerWidth*.05);

    p.container.append(
        makeEl('div').attr( { 'id': 'labelProgressBarHeader', 'class': 'progressBarHeader' } )
                     .css( { width: progressBarWidth,
                             left: progressBarLeft,
                             top: containerOffset.top + 15 } ).addClass('smallText').text('Adding Labels'),

        makeEl('div').attr( { 'id': 'labelProgressBar', 'class': 'progressBar' } )
                     .css( { width: progressBarWidth,
                             left: progressBarLeft,
                             top: containerOffset.top + 30 } ) );
        
    $('#labelProgressBar').progressbar();

}

function findClosestClade( p ) {

    var cladeCoords = p.clade.coords;

    var relativeCladeX = ( cladeCoords[p.scaling].x * p.scaleValue.x ) + p.offset.x;
    var relativeCladeY = ( cladeCoords.y * p.scaleValue.y ) + p.offset.y;

    var distance = Math.sqrt( Math.pow( p.x - relativeCladeX, 2) + Math.pow( p.y - relativeCladeY, 2) );

    if( distance < p.currentDistance ) { p.currentDistance = distance; p.closestClade = p.clade; }

    var childCount = p.clade.children.length;
        
    if( childCount ) {

        var recursiveParams =
            { x: p.x, y: p.y, closestClade: p.closestClade, currentDistance: p.currentDistance,
              scaleValue: p.scaleValue, scaling: p.scaling, offset: p.offset };

        var firstChildRelativeY =  ( p.clade.children[0].coords.y * p.scaleValue.y ) + p.offset.y;
        var lastChildRelativeY =  ( p.clade.children[ childCount - 1 ].coords.y * p.scaleValue.y ) + p.offset.y;

        if( p.y < firstChildRelativeY ) {
            recursiveParams.clade = p.clade.children[0];
            var result = findClosestClade( recursiveParams );
            if( result.clade.id != p.closestClade.id ) { return { clade: result.clade, distance: result.distance }; }
        }

        else if( p.y > lastChildRelativeY ) {
            recursiveParams.clade = p.clade.children[ childCount - 1 ];
            var result = findClosestClade( recursiveParams );
            if( result.clade.id != p.closestClade.id ) { return { clade: result.clade, distance: result.distance }; }
        }

        else {
            for( var i = 0, ii = childCount; i < ii; i++ ) {
               recursiveParams.clade = p.clade.children[ i ];
               var result = findClosestClade( recursiveParams );
               if( result.clade.id != p.closestClade.id ) {
                   recursiveParams.closestClade = p.closestClade = result.clade;
                   recursiveParams.currentDistance = p.currentDistance = result.distance;
               }
            }
        }
    }

    return { clade: p.closestClade, distance: p.currentDistance };
}

function findClade( p ) {

    if( p.clade.id == p.id ) { return p.clade; }

    for( var i = 0, ii = p.clade.children.length; i < ii; i++ ) {
        var rv = findClade( { clade: p.clade.children[i], id: p.id } );
        if( rv ) { return rv; }
    }
}
